StrictEnvironment : EnvironmentRedirect {
	at {
		arg key;
		var val = super.at(key);
		if (val.isNil) {
			Error("StrictEnvironment variable % is nil.".format(key)).throw
		}
		^val;
	}
}

+ SequenceableCollection {
	extractRange { | test |
		// given an ordered collection, gives the subrange satisfying the test
		// test should return -1, 0, 1 for too low, match, too high
		var left  = this.binSearchDir( \left, test );
		var right = this.binSearchDir( \right, test );
		^if( left.isNumber && right.isNumber, {
			this[ left.asInt .. right.asInt ]
		}, {
			[]
		} )
	}

	binSearchDir { | dir=\left, test, idx, step |
		/* example:
			[ 0, 1, 2, 3, 4, 5, 5, 7, 8, 9 ].binSearchDir( \right,
			{ | x | case { x < 5 } { -1 } { x == 5 } { 0 } { x > 5 } { 1 } } )
		*/
		// find rightmost or leftmost match for test in the collection
		// test should return -1, 0, or 1 for too low, match, or too high
		// collection should be ordered in regards to the test
		var result;
		if( this.isEmpty, { ^this } );
		if( idx.isNil, { idx = ( this.size / 2 ).round; step = ( idx / 2 ) } );
		if( or( step < 0.5, idx > ( this.size - 2 ) ), {
			^if( test.( this|@|idx ) == 0, {
				idx.clip( 0, ( this.size - 1 ) )
			}, {
				false
			} )
		} );
		^switch( test.( this@idx ),
			0, {
				result = switch( dir,
					\left, {
						this.binSearchDir( \left, test,
							idx - step.round, step / 2 );
					},
					\right, {
						this.binSearchDir( \right, test,
							idx + step.round, step / 2 );
					} );
				if( result.isNumber, result, idx )
			},
			1, { this.binSearchDir( dir, test, idx - step.round, step / 2 ) },
			-1, { this.binSearchDir( dir, test, idx + step.round, step / 2 ) } )
	}
}
